import subprocess
import time
import re
import threading
import logging
from enum import Enum
from common_func import print_with_location
from common_func import ConnectionStatus


class WiFi:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            with cls._lock:
                if not cls._instance:
                    cls._instance = super(WiFi, cls).__new__(cls)
        return cls._instance

    def __init__(
        self,
        status_callback=None,
        check_interval=3,
        ssid=None,
        password=None,
        security=None,
    ):
        if hasattr(self, "_initialized") and self._initialized:
            return  # 防止重复初始化
        self.process = None
        self.status = ConnectionStatus.DISCONNECTED
        self._monitor_thread = None
        if hasattr(self, "_initialized") and self._initialized:
            return

        self.status = ConnectionStatus.DISCONNECTED
        self._monitor_thread = None
        self._stop_event = threading.Event()
        self.check_interval = check_interval
        self.status_callback = status_callback
        self._lock = threading.Lock()
        self._ssid = ssid
        self._password = password
        self._security = security
        self.reconnect_count = 0
        self._initialized = True

    def scan_wifi_list(self):
        try:
            self._bring_up_interface()  # 启动无线网卡
            output = subprocess.check_output(
                "iwlist wlan0 scanning | grep -E 'ESSID|Signal level'",
                shell=True,
                universal_newlines=True,
            )

            wifi_list = []
            lines = output.splitlines()
            current_ssid = None
            current_signal = None

            for line in lines:
                line = line.strip()
                if line.startswith("ESSID:"):
                    current_ssid = line.split("ESSID:")[1].strip().strip('"')
                elif "Signal level=" in line:
                    match = re.search(r"Signal level=([-0-9]+)", line)
                    if match:
                        current_signal = int(match.group(1))

                if current_ssid is not None and current_signal is not None:
                    wifi_list.append(
                        {
                            "ssid": current_ssid,
                            "rssi": current_signal,
                            "security": "",  # 如果你之后需要识别安全性也可以加
                        }
                    )
                    current_ssid = None
                    current_signal = None

            print_with_location(f"Found WiFi list: {wifi_list}")
            return wifi_list

        except subprocess.CalledProcessError as e:
            print_with_location(f"Scan failed: {e}")
            return []

    def connect_wifi(self, ssid: str, password: str, security: str = None):
        self.__update_status(ConnectionStatus.CONNECTING)
        self._ssid = ssid
        self._password = password
        self._security = security
        try:
            self._bring_up_interface()  # 启动无线网卡
            subprocess.run(
                f"wpa_passphrase {ssid} {password} > /etc/wpa_supplicant.conf",
                shell=True,
                check=True,
            )
            time.sleep(1)
            subprocess.run(
                "wpa_supplicant -D nl80211 -B -i wlan0 -c /etc/wpa_supplicant.conf",
                shell=True,
                check=True,
            )
            time.sleep(1)
            subprocess.run(
                "udhcpc -R -n -p /var/run/udhcpc.wlan0.pid -i wlan0",
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True,
            )
            time.sleep(1)
            ip = self.get_wifi_ip()
            print_with_location(f"wifi local IP address: {ip}")
        except subprocess.CalledProcessError as e:
            print_with_location(f"WiFi connect failed: {e}")
            self.__update_status(
                ConnectionStatus.FAILED, 300, "wifi connect failed str{e}"
            )

    def get_wifi_ip(self):
        try:
            result = subprocess.run(
                "ifconfig wlan0",
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True,
            )

            if "wlan0" not in result.stdout:
                print_with_location("wlan0 interface not found.")
                self.__update_status(
                    ConnectionStatus.FAILED, 300, "wlan0 interface not found"
                )
                return None

            # Look for "inet addr:<IP>" format
            match = re.search(r"inet addr:(\d+\.\d+\.\d+\.\d+)", result.stdout)
            if match:
                ip = match.group(1)
                print_with_location(f"wifi local IP address: {ip}")
                self.__update_status(ConnectionStatus.CONNECTED)
                return ip
            else:
                print_with_location("wifi address not found.")
                self.__update_status(
                    ConnectionStatus.FAILED, 300, "wifi connect exception"
                )
                return None

        except Exception as e:
            print_with_location(f"Failed to get IP address:{str(e)}")
            return None

    def kill_wlan0(self):
        try:
            subprocess.run(
                "killall wpa_supplicant",
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True,
            )

            ps = subprocess.run(
                "ps aux | grep 'wlan0'",
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                universal_newlines=True,
            )

            if ps.stdout.strip() == "":
                print_with_location("No wlan0 process found.")
                self.__update_status(ConnectionStatus.DISCONNECTED)
                return None

            lines = ps.stdout.strip().splitlines()
            for line in lines:
                pid = line.split()[0]
                subprocess.run(f"kill {pid}", shell=True)
                print_with_location(f"Killed wlan0 process with PID: {pid}")

        except Exception as e:
            print_with_location(f"Failed to kill wlan0 process:{ str(e)}")

    def start_monitoring(self):
        if self._monitor_thread is None:
            self._stop_event.clear()
            self._monitor_thread = threading.Thread(
                target=self._monitor_wifi_status,
                daemon=True,
            )
            self.reconnect_count = 0
            self._monitor_thread.start()

    def stop_monitoring(self):
        if self._monitor_thread:
            self._stop_event.set()
            self._monitor_thread.join()
            self._monitor_thread = None

    def _monitor_wifi_status(self):
        while not self._stop_event.is_set():
            if self.get_wifi_ip() is None:
                print_with_location("wlan0 disconnected, attempting to reconnect...")
                self.reconnect()
            time.sleep(self.check_interval)

    def _ping_target(self, target):
        try:
            print_with_location("[_ping_target] IN >>>>")
            result = subprocess.run(
                ["ping", "-I", "wlan0", "-c", "1", "-W", "1", target],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
            return result.returncode == 0
            print_with_location("[_ping_target] OUT <<<<")
        except Exception as e:
            print_with_location(f"Ping error: {e}")
            print_with_location("[_ping_target] OUT <<<<")
            return False

    def connect(self, ssid, password, security=None):
        try:
            if ssid and password:
                self.stop_monitoring()
                self.connect_wifi(ssid, password, security)
                self.start_monitoring()
            else:
                print_with_location("No previous SSID and password to reconnect.")
                return True
        except Exception as e:
            print_with_location(f"WiFi reconnect failed: {e}")
            return False

    def reconnect(self):
        try:
            if self.reconnect_count >= 1:
                return None
            self.reconnect_count += 1
            self.kill_wlan0()
            time.sleep(1)
            if self._ssid and self._password:
                self.connect_wifi(self._ssid, self._password)
            else:
                print_with_location("No previous SSID and password to reconnect.")
                self.reconnect_count = 0
        except Exception as e:
            print_with_location(f"WiFi reconnect failed: {e}")
            self.reconnect_count = 0

    def disconnect(self):
        self.stop_monitoring()
        self.kill_wlan0()
        self.__update_status(ConnectionStatus.DISCONNECTED)

    def __update_status(self, new_status, code=0, msg=None):
        with self._lock:
            self.status = new_status
            if self.status_callback:
                self.status_callback(
                    status=new_status.value,
                    hwt_ype="wifi",
                    ssid=self._ssid,
                    error_msg=msg,
                    error_code=code,
                )

    @property
    def get_status(self):
        with self._lock:
            return self.status.value

    @property
    def get_current_ssid(self):
        return self._ssid

    def _bring_up_interface(self):
        try:
            subprocess.run("ifconfig wlan0 up", shell=True, check=True)
            print_with_location("Interface wlan0 is up.")
        except subprocess.CalledProcessError as e:
            print_with_location(f"Failed to bring up wlan0: {e}")

    def _bring_down_interface(self):
        try:
            subprocess.run("ifconfig wlan0 down", shell=True, check=True)
            print_with_location("Interface wlan0 is down.")
        except subprocess.CalledProcessError as e:
            print_with_location(f"Failed to bring down wlan0: {e}")
